package com.yklx.youke.service;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@Service
public class MfaService {

    private static final String HMAC_ALGORITHM = "HmacSHA1";
    private static final int SECRET_SIZE = 10;
    private static final int NUM_SCRATCH_CODES = 5;
    private static final int SCRATCH_CODE_SIZE = 4;

    public Map<String, Object> setupMfa(UserDetails userDetails) {
        // Generate a secret key for the user
        String secret = generateSecret(SECRET_SIZE);
        String qrCodeUrl = generateQRCodeUrl(userDetails.getUsername(), secret);

        // Return the secret and QR code URL for setup
        Map<String, Object> response = new HashMap<>();
        response.put("secret", secret);
        response.put("qrCodeUrl", qrCodeUrl);
        response.put("backupCodes", generateBackupCodes());

        return response;
    }

    public boolean verifyCode(String secret, String code) {
        if (secret == null || code == null) {
            return false;
        }

        // Validate the code using TOTP algorithm
        long timeIndex = Instant.now().getEpochSecond() / 30; // 30 second windows for TOTP

        for (int i = -1; i <= 1; i++) {
            if (getCode(secret, timeIndex + i).equals(code)) {
                return true;
            }
        }

        return false;
    }

    public boolean validateMfa(String secret, String code, String backupCode) {
        if (code != null && verifyCode(secret, code)) {
            return true;
        }

        if (backupCode != null && isValidBackupCode(backupCode)) {
            // Mark backup code as used
            useBackupCode(backupCode);
            return true;
        }

        return false;
    }

    private String generateSecret(int length) {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; // Base32 alphabet
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }

    private String generateQRCodeUrl(String username, String secret) {
        // In a real implementation, this would generate a proper QR code URL
        // for Google Authenticator or similar apps
        // Format: otpauth://totp/Application Name:username?secret=secret&issuer=Application Name
        return "otpauth://totp/YouKe:" + username + "?secret=" + secret + "&issuer=YouKe";
    }

    private String getCode(String secret, long timeIndex) {
        byte[] decodedKey = Base64.getDecoder().decode(Base64.getEncoder().encodeToString(secret.getBytes()));

        byte[] data = new byte[8];
        long value = timeIndex;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (byte) value;
        }

        SecretKeySpec signKey = new SecretKeySpec(decodedKey, HMAC_ALGORITHM);
        try {
            Mac mac = Mac.getInstance(HMAC_ALGORITHM);
            mac.init(signKey);
            byte[] hash = mac.doFinal(data);

            int offset = hash[hash.length - 1] & 0xF;

            // Get 4-byte window
            long truncatedHash = 0;
            for (int i = 0; i < 4; ++i) {
                truncatedHash <<= 8;
                truncatedHash |= (hash[offset + i] & 0xFF);
            }

            truncatedHash &= 0x7FFFFFFF;
            truncatedHash %= 1000000;

            String result = String.valueOf(truncatedHash);
            while (result.length() < 6) {
                result = "0" + result;
            }

            return result;
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Error generating MFA code", e);
        }
    }

    private String[] generateBackupCodes() {
        String[] codes = new String[NUM_SCRATCH_CODES];
        Random random = new Random();
        for (int i = 0; i < NUM_SCRATCH_CODES; i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < SCRATCH_CODE_SIZE; j++) {
                sb.append(random.nextInt(10));
            }
            codes[i] = sb.toString();
        }
        return codes;
    }

    private boolean isValidBackupCode(String backupCode) {
        // In a real implementation, this would check against stored backup codes
        // for the specific user in the database
        return backupCode != null && backupCode.matches("\\d{4}");
    }

    private void useBackupCode(String backupCode) {
        // In a real implementation, this would mark the backup code as used
        // so it can't be used again
    }
}